就像在腳本組件與擴充功能的執行階段,這段章節所提到的,腳本組件之間無法直接進行溝通,要依靠事件趨動的原理來處理各種操作邏輯,接下來我們就來探討這部份的實作細節及原理。
相關API: runtime.sendMessage 及 tabs.sendMessage
chrome.runtime.sendMessage(string extensionId, any message, object options, function responseCallback)
chrome.tabs.sendMessage(integer tabId, any message, function responseCallback)
兩個API的使用方法類似,以下是用法歸納:
runtime.sendMessage
:向擴充功能內的其他部件傳送訊息(!! 但內容腳本除外),此外使用這個方法也可以向其他的擴充功能傳遞訊息,此時需附上另一個擴充功能的ID,如果省略ID,則消息只會在擴充功能內部傳送。tabs.sendMessage
:把訊息傳送給內容腳本
的專屬API,需附上tabID作為參數,讓Chrome知道他要傳送訊息的對像是哪個內容腳本。(注意不同頁籤之間並不共享內容腳本,內容腳本在每個頁籤都會單獨注入)。以下程式碼範例,示範了從內容腳本發送單次請求。並且在回調中可使用response
參數接收訊息的回傳。
chrome.runtime.sendMessage({greeting: "你好"}, function(response) {
console.log(response);
});
如果從內容腳本向擴充功能執行緒發送請求,與上面的作法類似,唯一的差別就是需指定發送對象,所以要把tabID作為參數傳入。
以下程式碼示範了,利用chrome.tabs.query
取得當前tabID(tabs[0].id
),並向目前使用者Focus的頁籤發送請求。
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { greeting: "你好" }, function(response) {
console.log(response.farewell);
});
});
chrome.tabs.query允許使用者用物件作為第一個參數查詢使用者目前開啟的所有書籤,在上面的程式碼中示範了在目前使用的視窗下,取得目前瀏覽的頁籤。回傳值會是一個tab物件,內含了網站url、title、等有用資訊。
相關API: runtime.onMessage
chrome.runtime.onMessage.addListener(function callback)
設置chrome.runtime.onMessage.addListener
來接收訊息:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"取得到tab,這是來自內容腳本的訊息:" + sender.tab.url
: "沒有tab,這是來自擴充功能內部的訊息");
if (request.greeting == "你好")
sendResponse({farewell: "再見"});
});
如果有多個訂閱的設置(onMessage)同時都回傳訊息,只有其中一個
sendResponse()
能發送成功。
實作功能說明:
彈出視窗腳本:
document.addEventListener('DOMContentLoaded', function(dcle) {
var dButtonEvent = document.getElementById("button1");
var dButtonContent = document.getElementById("button2");
//點擊按鈕,向事件腳本發送訊息
dButtonEvent.addEventListener('click', function(ce) {
chrome.runtime.sendMessage({ content: "你好,此訊息來自彈出視窗腳本" }, function(response) {
console.log(response);
});
});
//點擊按鈕,向內容腳本發送訊息
dButtonContent.addEventListener('click', function(ce) {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { content: "你好,此訊息來自彈出視窗腳本" }, function(response) {
console.log(response);
});
});
});
});
事件腳本:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
console.log(message);
console.log(sender);
sendResponse({content: "來自事件腳本的回覆"});
});
內容腳本:
console.log("內容腳本注入");
var toggleBg = true;
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
console.log(message);
console.log(sender);
sendResponse({ content: "來自內容腳本的回覆" });
if (toggleBg) {
document.body.style.backgroundColor = "red";
toggleBg = !toggleBg;
} else {
document.body.style.backgroundColor = "black";
toggleBg = !toggleBg;
}
});
結果展示:向事件腳本發送訊息 (右下為彈出視窗腳本,左下為事件腳本)
結果展示:向內容腳本發送訊息 (右下為彈出視窗腳本,左下為內容腳本)
完整範例在Github
如果上個段落討論的一次性溝通,比喻為信件往來,那麼我們可以把長時間的連接當成打電話。
長時間連接的好處,在於通話其間雙方能共享狀態,另一方面也能讓訊息的傳遞具有針對性。讓我們來探討一個使用情境:如果你想要實作一個「表單自動填充功能」,擴充套件在內容腳本偵測到表單元素時,開啟了"表單填寫"的通話,事件腳本在接受通話的請求後,內容腳本可以在使用者聚焦到不同的表單元件時,將表單元件的訊息傳送給事件腳本,事件腳本可以判斷有無符合的資料並回傳給內容腳本自動填入。
相關API:runtime.connect 及 tabs.connect
chrome.runtime.connect(string extensionId, object connectInfo)
chrome.tabs.connect(integer tabId, object connectInfo)
不管是發送還是接收,腳本都會獲得一個回傳的 runtime.Port物件,我們將通過他來接收跟發送訊息。
以下腳本示範如何從內容腳本建立連接,並且發送及監聽訊息。
var dButtonConnect1 = document.getElementById("button1");
var dButtonConnect2 = document.getElementById("button2");
var port = chrome.runtime.connect({ name: "一通電話" });
port.onMessage.addListener(function(response) {
console.log(response);
switch (response.msg) {
case "是的,他在":
port.postMessage({ msg: "請幫我把電話她" });
break;
case "不,他不在":
port.postMessage({ msg: "請幫我留言給他,留言是XXXXXX" });
break;
default:
break;
}
});
dButtonConnect1.addEventListener('click', function(event) {
port.postMessage({ msg: "請問羅拉拉在嗎" });
});
dButtonConnect2.addEventListener('click', function(event) {
port.postMessage({ msg: "請問王小明在嗎" });
});
與上面提到的一次性連接範例類似,如果你是向容容腳本接立長時間連結,可以把runtime.connect,提換成 tabs.connect,並使用tabs.query
附上tabID。
chrome.runtime.onConnect.addListener(function callback)
通訊發生時,回調會回傳port物件,供你接收及發送訊息。
chrome.runtime.onConnect.addListener(function(port){
if(port.name == "一通電話"){
port.onMessage.addListener(function(response) {
console.log(response);
switch (response.msg) {
case "請問羅拉拉在嗎":
port.postMessage({ msg: "是的,他在" });
break;
case "請問王小明在嗎":
port.postMessage({ msg: "不,他不在" });
break;
default:
port.postMessage({ msg: "好的" });
port.disconnect();
break;
}
});
}
});
如果你想知道某個通話的連線狀態,可以調用runtime.Port.onDisconnect方法,當通話其中一方使用runtime.Port.disconnect 結束對話或是通訊的頁面被關閉,就會收到通知。
上面的範例結果展示:右上為背景頁面,右下為彈出視窗腳本
完整範例在Github
內容腳本
傳遞訊息,都要使用tabs底下的API(tabs.sendMessage 以及tabs.connect ),並且使用tabs.query取得想發送的頁籤ID。至於內容腳本的訊息接收跟其他腳本組件並無差異(內容腳本可以正常使用runtime.onMessage以及runtime.onConnect )。